Angular 与 RxJS 之 Async pipe

您所在的位置:网站首页 angular ts Angular 与 RxJS 之 Async pipe

Angular 与 RxJS 之 Async pipe

#Angular 与 RxJS 之 Async pipe| 来源: 网络整理| 查看: 265

Angular 中的 observable 在 Angular 中很多概念本质上是一个 observable,比如: 使用 httpClient call API 的返回 使用 ActivatedRoute 获得的 URL params 或者是 queryParams 使用 Angular Form 时需要获得 Form 的变化,valueChanges 使用 Component 中 Output EventEmitter 当然,还有一些概念其实我们也希望它是一个 observable,目前可能还不是,或者支持的还不够好: Component 中 @Input 的数据变化 (NgChanges => Observable)(其实可以看出,@Input 本质上是一个 Observable, @Output 本质上是一个 Observer,双向绑定本质上是一个 Subject。) Component 中的 @Input 直接用 Subject 替换 EventEmitter(目前其实两者兼容)。 Component 的生命周期函数 => Observable,比如说,ngDestroy。这样我们就不再需要将逻辑拆分到不同的 生命周期函数中。 Template 中支持的还不够好。 Event to Observable 目前定义起来还比较麻烦。 Angular 中的 Async Pipe

正是因为在 Angular 中很多概念都已经 To Obserable,那在 Template 中如果能够直接使用 Observable 就可以使得逻辑简单很多。举个最简单的例子:

有一个 API /users/:userId 返回 User 的具体信息,User ID 从 URL 中读取后现实具体的信息在页面上。其实逻辑很简单:

const user$ = this.activeRoute.params.pipe( distinctUntilChanged(params => params.id), switchMap(id => { return this.service.getUser(id); }), );

页面数据的显示也比较简单,直接 subscribe 然后赋值给一个 public 变量就好了。

user$.subscribe(user => this.user = user); {{ user | json}}

当然,这里有一些其他的问题,比如你不得不再定义一个 user 变量,比如说你需要多写无脑的 subscribe。比如说,你需要考虑是否需要 unsubscribe. (关于什么时候需要 unsubscribe 我们可以以后单开一篇来讲。)

我们是否可以将那些无脑的 subscribe 和 unsubscribe 和定义重复的变量交给框架层来做呢,答案当然就是 async pipe.

{{ user$ | async | json }}

如果 template 中其他地方需要使用还可以:

{{user?.name}}

这样,你不仅不需要定义一个跟 user$ 名字一样的全局变量(当然可以用是否有 $ 来却分 observable 和普通的值)。Angular 本省也会在 component 销毁的时候自动帮你 unsubscribe。省掉了很多重复性的工作。

当然,async 的用法还不止如此,还可以在 *ngFor 或者 component input 中使用,比如:

{{user?.name}}

或者:

这其实就帮你解决了大部分你需要 subscribe 以后再 bind 到 template 中的情况。

Angular 中的脏检查和 Change Detection

熟悉 Angular 或者其前身 AngularJS 的话,对脏检查应该不会陌生。简单的说,当 component 中跟 template bind 的数据变化以后,如何通知页面发生变化呢,比如说:

// Template {{user | json}} // Component.ts setTimeout(() => { this.user = {}; }, 3000);

当 3 秒钟以后,user 发生变化,Angular 是如何知道 当前 component 的 HTML 要更新呢,答案就是脏检查。最简单的例子就是比如说,每隔一段时间检查一次 component 中所有的字段有没有变化,只要有变化,HTML重新渲染一次。当然真实情况要复杂的多,为了节约资源,Angular 会在任何有可能发生数据变化的情况做检查,@Input 变化,有 click event,setTimeout 之类。

这就是,我们所说的脏检查。

那么,这里跟我们所说的 async 有什么关系呢?虽然在 Angular 中,脏检查已经不再是一件那么耗费性能的事情,也不再需要包裹 JS 中的函数来实现脏检查。但是我们想,Angular 在默认情况下,做的事情永远是尽可能的减少不必要的检查,逼近真实,有没有一种可能性是,当 Template 中的数据发生变化的时候我们主动的通知 Angular,其他情况,Angular 不做检查。

这就是 ChangeDetectionStrategy, onPush 它只在必要的时候,做数据变化的检查,也就是:

Input 变化的时候 Event 或者 @Output Event 发生的时候 Async pipe 对应的流 emit value 的时候 主动 Detect changes 的时候

这样,就能尽可能的减少脏检查,实现类似 Vue 中的 Push Detect changes。这样其实就有要求,我们在 Template 上原则上只能绑定两类数据:

基于 Input 或者 Event 变化的数据 通过 Async bind 的数据 所以说,Async 另一个重要的功能就是,结合 ChangeDetectionStrategy 实现的摆脱对于 Angular 脏检查的依赖,实现性能的优化。 Async pipe 的实际使用

实际上,Angular 对于 observable 只提供了 async pipe,在 template 中使用是非常受限的,也会有很多坑。举个例子:

{{user$ | async}}

这是一个初接触 async 经常会范的错误,也是容易忽视的问题,因为如果把 user$ 换成普通值是完全没有问题的。但是 流数据 user$ 却完全不同,因为每一次 async pipe, 实际上相当于执行了一次 subscribe,根据之前的文章讲的内容,相同的 observable,subscribe 多次,其实的彼此独立的,举个最简单的例子,假设刚刚例子中的:

this.user$ = this.httpClient.get('/user/xxx');

你会发现,ajax call 发生的多次。当然解决方法也有很多,你可以将 this.user$.pipe(share()),这样就可以将冷的 observable 变热。

个人比较推荐的做法是:尽可能不在 component 中手动 subscribe 流数据,尽可能不 async pipe 多次同一个流数据。

实现的方法其实也恨简单,就是尽可能将 async | pipe 包裹在更外层。比如上面的例子可以这样实现:

{{user}}

细心的话可能就发现了另外一个问题,这两者并不完全等价,在 user$ emit value 前,包裹的部分会处于 ngIf flase 中,也就是 HTML 会消失,这常常并不是我们的本意。我们的目的只是为了要 async pipe 流数据,但是却并不想让它包裹的范围影响显示。本质上我们需要利用的是变量的 scope 范围。

遗憾的是,Angular 中并没有除了,*ngIf, *ngFor, @Input pipe 之外的方法。当然,简单写个 hack 就可以实现了,比如说,我们让 ngIf 永远不是 false。最简单的就是将我们的值包裹在一个 object 中,这样最差的情况也是 {},从而实现非空。举个例子: {{state.user}}

当然,你也可以使用  ngrx/component 中提供的 ngrxLet,看起来会更加简洁:

{{user}} 所以说,第二个经验就是:如果你只想使用流数据并不想影响 HTML,你可以使用非 false 的 *ngIf 或者使用ngrxLet。

当然,这不可能是最后一个坑,还有很多其他的坑,在实际的使用中,我们往往不会只有一个 流数据,而且 template 中使用起来可能错综复杂。而且,*ngIf是不能在同一个 HTML 标签上 apply 两次的。这里就涉及到处理复杂 async pipe 的三种方法:

利用 ng-container 不占用 HTML 位置的特性: xxxx

当然,这样写,无疑让简单的代码看起来更加复杂了,虽然最后生成的 HTML 还是:xxxx

利用 *ngIf 实现多个数据流的包裹: xxxx

实际上,我们可以总结出一个类似套路的解法,就是在 component 的最外层,包裹一层 ng-container,作为 component state 来处理所有的数据。

当然,还有一个类似的套路就是在 component 中将所有的 流数据 通过 combineLatest 包裹起来,从而实现类似的效果。(当然,这里并不完全一致,我们先不说)。当然,个人认为最方便的还是通过实现一个简单的 state 方法来实现。 虽然,*ngIf 不能一行写多个,但是 component 完全支持多个 input 的 async pipe, 所以,尽可能将 component 拆的细致,从而通过 component 的 input 实现多个 async pipe 也是一种方法。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3